#include "str.h"

#include <string.h>
#include <algorithm>
#include <stdexcept>

#include "scope_guard.h"


namespace evm { namespace str {

string lower(const string& str) {
    return lower(string(str));
}

string lower(string&& str) {
    transform(str.begin(), str.end(), str.begin(), ::tolower);
    return str;
}

string lower(const char* str) {
    return lower(string(str));
}

string upper(const string& str) {
    return upper(string(str));
}

string upper(string&& str) {
    transform(str.begin(), str.end(), str.begin(), ::toupper);
    return str;
}

string upper(const char* str) {
    return upper(string(str));
}

bool startswith(const string &str, const string &prefix, size_type start, size_type end) {
    end = end < str.size() ? end : str.size();
    if (start > end || start + prefix.size() > end)
        return false;
    end = start + prefix.size();
    return std::equal(str.cbegin() + start, str.cbegin() + end, prefix.c_str());
}

bool startswith(const string &str, const char* prefix, size_type start, size_type end) {
    end = end < str.size() ? end : str.size();
    auto prefix_len = strlen(prefix);
    if (start > end || start + prefix_len > end)
        return false;
    end = start + prefix_len;
    return std::equal(str.cbegin() + start, str.cbegin() + end, prefix);
}

bool endswith(const string &str, const string &suffix, size_type start, size_type end) {
    end = end < str.size() ? end : str.size();
    if (start > end || start + suffix.size() > end)
        return false;
    start = end - suffix.size();
    return std::equal(str.cbegin() + start, str.cbegin() + end, suffix.c_str());
}

bool endswith(const string &str, const char* suffix, size_type start, size_type end) {
    end = end < str.size() ? end : str.size();
    auto suffix_len = strlen(suffix);
    if (start > end || start + suffix_len > end)
        return false;
    start = end - suffix_len;
    return std::equal(str.cbegin() + start, str.cbegin() + end, suffix);
}

bool contains(const string &str, const string &substr) {
    return contains(str, substr.c_str());
}

bool contains(const string &str, const char* substr) {
    return str.find(substr) != string::npos;
}

bool contains(const string &str, char ch) {
    return str.find(ch) != string::npos;
}

namespace {
size_type count(const string &str, const char *substr, size_type substr_len) {
    size_type n = 0;
    size_type pos = 0;
    while (((pos = str.find(substr, pos)) != string::npos)) {
        ++n;
        pos += substr_len;
    }
    return n;
}
}

size_type count(const string &str, const string &substr) {
    return count(str, substr.c_str(), substr.size());
}

size_type count(const string &str, const char *substr) {
    return count(str, substr, strlen(substr));
}

bool iequals(const string &str1, const string &str2) {
    if (str1.size() != str2.size())
        return false;
    return std::equal(str1.cbegin(), str1.cend(), str2.cbegin(),
                      [](char c1, char c2) {
                          return std::tolower(c1) == std::tolower(c2);
                      });
}

vector<string> split(const string &str, int maxsplit) {
    static const string SPACES_STR = SPACES;

    maxsplit = maxsplit < 0 ? numeric_limits<int>::max() : maxsplit;

    vector<string> res;
    size_type pos = 0;
    string s;
    for (int i = 0; i < maxsplit;) {
        size_type j = pos;
        for (; j < str.size(); ++j) {
            if (!contains(SPACES_STR, str[j]))
                s += str[j];
            else {
                pos = str.size();
                for (size_type k = j + 1; k < str.size(); ++k) {
                    if (!contains(SPACES_STR, str[k])) {
                        pos = k;
                        break;
                    }
                }
                if (!s.empty()) {
                    res.push_back(std::move(s));
                    ++i;
                }
                break;
            }
        }
        if (j == str.size())
            break;
    }

    if (str.cbegin() + pos < str.cend())
        res.emplace_back(str.cbegin() + pos, str.cend());

    return res;
}

namespace {
vector<string> split(const string &str, const char* sep, size_type sep_len, int maxsplit) {
    if (sep_len == 0)
        throw invalid_argument("empty separator");

    maxsplit = maxsplit < 0 ? numeric_limits<int>::max() : maxsplit;

    vector<string> res;
    size_type pos = 0;
    for (int i = 0; i < maxsplit; ++i) {
        auto xpos = str.find(sep, pos);
        if (xpos == string::npos)
            break;

        res.emplace_back(str.cbegin() + pos, str.cbegin() + xpos);
        pos = xpos + sep_len;
    }

    if (str.cbegin() + pos <= str.cend())
        res.emplace_back(str.cbegin() + pos, str.cend());

    return res;
}
}

vector<string> split(const string &str, const string &sep, int maxsplit) {
    return split(str, sep.c_str(), sep.size(), maxsplit);
}

vector<string> split(const string &str, const char* sep, int maxsplit) {
    return split(str, sep, strlen(sep), maxsplit);
}

string slice(const string& str, int64_t start, int64_t stop, int64_t step) {
    if (step == 0)
        throw invalid_argument("slice step cannot be zero");

    if (start < 0) {
        start = str.size() + start;
        if (start < 0)
            start = 0;
    }
    else if (start > static_cast<int64_t >(str.size()))
        start = str.size();

    if (stop < 0) {
        stop = str.size() + stop;
        if (stop < 0)
            stop = 0;
    }
    else if (stop > static_cast<int64_t >(str.size()))
        stop = str.size();

    string res;
    if (step > 0) {
        for (int64_t pos = start; pos < stop; pos += step)
            res += str[pos];
    }
    else {
        for (int64_t pos = start; pos > stop; pos += step)
            res += str[pos];
    }
    return res;
}

namespace {
string replace(const string &str, const char* old, const char* new_, size_type old_len, size_type count) {
    string res;
    size_type pos = 0;
    for (size_type i = 0; i < count; ++i) {
        auto xpos = str.find(old, pos);
        if (xpos != string::npos) {
            res.insert(res.end(), str.cbegin() + pos, str.cbegin() + xpos);
            res += new_;
            pos = xpos + old_len;
            if (pos >= str.size())
                break;
        }
        else
            break;
    }
    if (pos < str.size())
        res.insert(res.end(), str.cbegin() + pos, str.cend());
    return res;
}
}

string replace(const string &str, const string &old, const string &new_, size_type count) {
    return replace(str, old.c_str(), new_.c_str(), old.size(), count);
}

string replace(const string &str, const char* old, const char* new_, size_type count) {
    return replace(str, old, new_, strlen(old), count);
}

string strip(const string& str, const string& chars) {
    return strip(str, chars.c_str());
}

string strip(const string& str, const char* chars) {
    auto start = str.find_first_not_of(chars);
    if (start == string::npos)
        return "";
    auto end = str.find_last_not_of(chars);
    return string(str, start, end != string::npos ? (end - start + 1) : string::npos);
}

string strip(string&& str, const string& chars) {
    return strip(std::move(str), chars.c_str());
}

string strip(string&& str, const char* chars) {
    str.erase(0, str.find_first_not_of(chars));
    auto pos = str.find_last_not_of(chars);
    str.erase(pos != string::npos ? pos + 1 : 0);
    return str;
}

string lstrip(const string& str, const string& chars) {
    return lstrip(str, chars.c_str());
}

string lstrip(const string& str, const char* chars) {
    auto pos = str.find_first_not_of(chars);
    if (pos == string::npos)
        return "";
    return string(str, pos);
}

string lstrip(string&& str, const string& chars) {
    return lstrip(std::move(str), chars.c_str());
}

string lstrip(string&& str, const char* chars) {
    str.erase(0, str.find_first_not_of(chars));
    return str;
}

string rstrip(const string& str, const string& chars) {
    return rstrip(str, chars.c_str());
}

string rstrip(const string& str, const char* chars) {
    auto pos = str.find_last_not_of(chars);
    return string(str, 0, pos != string::npos ? pos + 1 : 0);
}

string rstrip(string&& str, const string& chars) {
    return rstrip(std::move(str), chars.c_str());
}

string rstrip(string&& str, const char* chars) {
    auto pos = str.find_last_not_of(chars);
    str.erase(pos != string::npos ? pos + 1 : 0);
    return str;
}

string strerror(int err) {
    int savedErrno = errno;

    // Ensure that we reset errno upon exit.
    auto guard(make_guard([&] { errno = savedErrno; }));

    char buf[1024];
    buf[0] = '\0';

    string result;

    // https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/strerror_r.3.html
    // http://www.kernel.org/doc/man-pages/online/pages/man3/strerror.3.html
#if (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE
    // Using XSI-compatible strerror_r
    int r = strerror_r(err, buf, sizeof(buf));
    if (r != 0)
        result = "Unknown error " + std::to_string(err)
                 + " (strerror_r failed with error " + std::to_string(errno) + ")";
    else
        result.assign(buf);
#else
    // Using GNU strerror_r
    result.assign(strerror_r(err, buf, sizeof(buf)));
#endif

    return result;
}

}}
